﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.UI;
using System.Web.UI.WebControls;

[assembly: WebResource("ExtJSExtender.Controls.Tree.TreePanelExtenderBehavior.js", "text/javascript")]

namespace ExtJSExtender.Controls
{
    [ToolboxData("<{0}:TreePane runat=\"server\"></{0}:TreePane>")]
    [ToolboxBitmap(typeof(TreeView))]
    public class TreePanel : ExtJSControlBase, IPostBackEventHandler, INamingContainer, IPostBackDataHandler, ICallbackEventHandler
    {
        #region 属性

        /// <summary>
        /// Header要显示的文本
        /// </summary>
        [ExtJSExtender.DescribableProperty("Title")]
        public string Title
        {
            get
            {
                if (ViewState["Title"] == null)
                    ViewState["Title"] = string.Empty;
                return ViewState["Title"].ToString();
            }
            set
            {
                ViewState["Title"] = value;
            }
        }

        /// <summary>
        /// 边框的宽度
        /// </summary>
        public Unit Border
        {
            get
            {
                if (ViewState["Border"] == null)
                    ViewState["Border"] = new Unit(1);
                return (Unit)ViewState["Border"];
            }
            set
            {
                ViewState["Border"] = value;
            }
        }

        /// <summary>
        /// 使用箭头作为展开图标
        /// </summary>
        [ExtJSExtender.DescribableProperty("UseArrows")]
        public bool UseArrows
        {
            get
            {
                if (ViewState["UseArrows"] == null)
                    ViewState["UseArrows"] = false;
                return (bool)ViewState["UseArrows"];
            }
            set
            {
                ViewState["UseArrows"] = value;
            }
        }

        /// <summary>
        /// 是否自动回发
        /// </summary>
        [ExtJSExtender.DescribableProperty("AutoPostBack")]
        public bool AutoPostBack
        {
            get
            {
                if (ViewState["AutoPostBack"] == null)
                    ViewState["AutoPostBack"] = false;
                return (bool)ViewState["AutoPostBack"];
            }
            set
            {
                ViewState["AutoPostBack"] = value;
            }
        }

        /// <summary>
        /// 获取数据的地址，在请求的时候，如果节点的id属性存在，则会作为请求参数传递
        /// </summary>
        [ExtJSExtender.DescribableProperty("DataUrl")]
        public string DataUrl
        {
            get
            {
                if (ViewState["DataUrl"] == null)
                    ViewState["DataUrl"] = string.Empty;
                return ViewState["DataUrl"].ToString();
            }
            set
            {
                ViewState["DataUrl"] = value;
            }
        }

        /// <summary>
        /// 树根节点的ID，通常用来加载第一层节点
        /// </summary>
        [ExtJSExtender.DescribableProperty("RootId")]
        public string RootId
        {
            get
            {
                if (ViewState["RootId"] == null)
                    ViewState["RootId"] = string.Empty;
                return ViewState["RootId"].ToString();
            }
            set
            {
                ViewState["RootId"] = value;
            }
        }

        /// <summary>
        /// 树根节点的文本，如果存在则显示根节点
        /// </summary>
        [ExtJSExtender.DescribableProperty("RootText")]
        public string RootText
        {
            get
            {
                if (ViewState["RootText"] == null)
                    ViewState["RootText"] = string.Empty;
                return ViewState["RootText"].ToString();
            }
            set
            {
                ViewState["RootText"] = value;
            }
        }

        private TreeNode rootNode = null;
        /// <summary>
        /// 树节点，未定义强类型TreeNode，因为Ext TreeNode中某些属性需要动态的处理
        /// 使用Dictionary字典来表示一个节点，必须使用System.Web.Script.Serialization中提供的JSON序列化和反序列化方法
        /// Dictionary中包含的Key和值的类型，具体请参考ExtJS4中的NodeInterface定义：
        /// id：string类型，节点的ID，必须
        /// text：string类型，节点显示的文本，必须
        /// parentId：string类型，父节点ID
        /// children：List<Dictionary<string, object>>类型，子节点集合
        /// root：bool类型，是否为根节点
        /// leaf：bool类型，是否为叶子节点
        /// checked：bool类型，是否选中，如果定义此属性，该节点前会显示选择框
        /// cls：string类型，icon的样式
        /// icon：string类型，icon的地址
        /// href：string类型，导航到的地址
        /// hrefTarget：string类型，导航地址打开的位置
        /// draggable：bool类型，是否可以拖动
        /// expanded：bool类型，是否展开
        /// </summary>
        [Bindable(false)]
        public TreeNode RootNode
        {
            get
            {
                if (rootNode == null)
                {
                    rootNode = new TreeNode();
                    rootNode.children = new TreeNodeCollection();
                    rootNode.expanded = true;
                }
                return rootNode;
            }
            set
            {
                rootNode = value;
            }
        }

        /// <summary>
        /// 选中的节点
        /// </summary>
        [Bindable(false)]
        public TreeNode SelectedNode
        {
            get;
            set;
        }

        private List<TreeNode> checkedNodes = null;
        /// <summary>
        /// 勾选的节点
        /// </summary>
        [Bindable(false)]
        public List<TreeNode> CheckedNodes
        {
            get
            {
                checkedNodes = new List<TreeNode>();
                this.ProcessNode(RootNode, (node) =>
                {
                    if (node.isChecked.HasValue && node.isChecked.Value) checkedNodes.Add(node);
                    return true;
                }, true);

                return checkedNodes;
            }
        }

        /// <summary>
        /// 行为状态
        /// </summary>
        [Bindable(false)]
        public TreeBehaviorState BehaviorState
        {
            get
            {
                if (ViewState["BehaviorState"] == null)
                    ViewState["BehaviorState"] = new TreeBehaviorState();
                return (TreeBehaviorState)ViewState["BehaviorState"];
            }
            set
            {
                ViewState["BehaviorState"] = value;
            }
        }

        [ExtJSExtender.DescribableProperty("TriggerPostBackEvents")]
        [PersistenceMode(PersistenceMode.InnerProperty), Bindable(true), Localizable(true)]
        public TreeTriggerPostBackEvevts TriggerPostBackEvents
        {
            get
            {
                if (ViewState["TriggerPostBackEvents"] == null)
                    ViewState["TriggerPostBackEvents"] = new TreeTriggerPostBackEvevts();

                return (TreeTriggerPostBackEvevts)ViewState["TriggerPostBackEvents"];
            }
            set
            {
                ViewState["TriggerPostBackEvents"] = value;
            }
        }


        /// <summary>
        /// 树节点，可以直接作为标签写在代码中
        /// </summary>
        [PersistenceMode(PersistenceMode.InnerProperty), Bindable(true), Localizable(true)]
        public TreeNodeCollection Nodes
        {
            get
            {
                return RootNode.children;
            }
            set
            {
                RootNode.children = value;
            }
        }

        #endregion

        #region 构造函数

        public TreePanel()
            : base(HtmlTextWriterTag.Div)
        {

        }

        #endregion

        #region 事件

        public event NodeClickEventHandler NodeClick;
        public event NodeSelectChangedEventHandler NodeSelectChanged;
        public event NodeCheckChangedEventHandler NodeCheckChanged;
        public event NodeExpandChangedEventHandler NodeExpand;
        public event NodeCollapseChangedEventHandler NodeCollapse;
        public event LoadChildrenEventHandler LoadChildren;

        #endregion

        #region 数据绑定

        /// <summary>
        /// 绑定DataTable
        /// </summary>
        /// <param name="table"></param>
        /// <param name="IdField"></param>
        /// <param name="TextField"></param>
        /// <param name="ParentIdField"></param>
        /// <param name="handler">在创建完一个节点之后，调用该方法，完成其它工作</param>
        public void BindDataTable(DataTable table, string IdField, string TextField, string ParentIdField, Action<DataRow, TreeNode> handler = null)
        {
            this.BindDataTable(table, this.RootNode, IdField, TextField, ParentIdField, handler);
        }

        /// <summary>
        /// 绑定DataTable
        /// </summary>
        /// <param name="table"></param>
        /// <param name="parent"></param>
        /// <param name="IdField"></param>
        /// <param name="TextField"></param>
        /// <param name="ParentIdField"></param>
        public void BindDataTable(DataTable table, TreeNode parent, string IdField, string TextField, string ParentIdField, Action<DataRow, TreeNode> handler = null)
        {
            //清除所有子节点
            parent.children.Clear();

            if (table == null
                || string.IsNullOrWhiteSpace(IdField)
                || string.IsNullOrWhiteSpace(TextField)
                || string.IsNullOrWhiteSpace(ParentIdField)
                ) return;

            string filter = string.Format("{0}='{1}'", ParentIdField, parent.id);
            DataRow[] rows = table.Select(filter);
            if (rows != null)
            {
                int count = rows.Count();
                for (int i = 0; i < count; i++)
                {
                    DataRow row = rows[i];
                    TreeNode node = new TreeNode();
                    node.id = (string)row[IdField];
                    node.text = (string)row[TextField];
                    node.expanded = false;

                    //绑定子节点
                    BindDataTable(table, node, IdField, TextField, ParentIdField, handler);

                    if (node.children.Count > 0)
                        node.leaf = false;
                    else
                        node.leaf = true;

                    if (handler != null)
                        handler.Invoke(row, node);

                    //添加到子节点集合
                    parent.children.Add(node);
                }
            }
        }

        #endregion

        #region 公用方法

        /// <summary>
        /// 处理树节点，包含子节点
        /// </summary>
        /// <param name="node">要处理的节点</param>
        /// <param name="handler">处理程序</param>
        /// <param name="includeChildren">是否处理子节点，默认为true</param>
        public void ProcessNode(TreeNode node, Func<TreeNode, bool> handler, bool includeChildren = true)
        {
            Func<TreeNode, bool> _handler = null;

            _handler = (_node) =>
            {
                bool isContinue = handler.Invoke(_node);

                if (includeChildren && isContinue && _node.children != null)
                {
                    foreach (TreeNode child in _node.children)
                    {
                        isContinue = _handler(child);
                        if (!isContinue) break;
                    }
                }

                return isContinue;
            };

            _handler.Invoke(node);
        }

        /// <summary>
        /// 根据节点ID得到一个树节点
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public TreeNode GetNodeById(string id)
        {
            TreeNode _node = null;
            this.ProcessNode(RootNode, (node) =>
            {
                if (node.id == id)
                {
                    _node = node;
                    return false;
                }
                return true;
            }, true);

            return _node;
        }

        #endregion

        #region 只读属性，处理控件的值及事件等

        [ExtJSExtender.DescribableProperty("NodeValueHiddenField")]
        public string NodeValue_HiddenField_ClientID
        {
            get { return this.ClientID + "_NodeValue"; }
        }

        [ExtJSExtender.DescribableProperty("BehaviorStateHiddenField")]
        public string BehaviorState_HiddenField_ClientID
        {
            get { return this.ClientID + "_BehaviorState"; }
        }

        #endregion

        #region WebControl 方法重写

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            //加载回发的数据
            this.LoadPostData(this.ClientID, this.Page.Request.Params);

            RootNode.id = this.RootId;
            RootNode.text = this.RootText;
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            if (!this.Page.IsPostBack)
            {
                // 处理根节点加载的问题，如果根节点未加载，并且是展开的状态，则执行LoadChildren事件
                if (!this.RootNode.loaded && this.RootNode.expanded.HasValue && this.RootNode.expanded.Value)
                {
                    if (LoadChildren != null)
                    {
                        LoadChildrenEventArgs args = new LoadChildrenEventArgs();
                        args.NodeId = RootNode.id;
                        args.Node = this.RootNode;
                        args.Node.expanded = true;
                        args.Node.loaded = true;

                        LoadChildren.Invoke(this, args);
                    }
                }
            }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);

            //将Node数据写在客户端隐藏域中
            Util.RenderHiddenField(writer, this.NodeValue_HiddenField_ClientID, Util.SerializeToJson(this.RootNode));

            //将BehaviorState写在客户端隐藏域中
            Util.RenderHiddenField(writer, this.BehaviorState_HiddenField_ClientID, Util.SerializeToJson(this.BehaviorState));
        }

        #endregion

        #region ExtScriptWebControlBase 成员

        public override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptControlDescriptor descriptor = new ScriptControlDescriptor("ExtJSExtender.Controls.TreePanelExtenderBehavior", this.ClientID);
            descriptor.AddProperty("Enabled", Enabled);
            descriptor.AddProperty("Width", this.Width.Value);
            descriptor.AddProperty("Height", this.Height.Value);
            descriptor.AddProperty("Border", this.Border.Value);
            descriptor.AddProperty("UniqueID", this.UniqueID);
            ExtJSExtender.Util.DescribeProperties(this, descriptor);

            var descriptors = base.GetScriptDescriptors().ToList();
            descriptors.Add(descriptor);
            return descriptors;
        }

        public override IEnumerable<ScriptReference> GetScriptReferences()
        {
            var reference = new ScriptReference();
            reference.Assembly = "ExtJSExtender.Controls";
            reference.Name = "ExtJSExtender.Controls.Tree.TreePanelExtenderBehavior.js";

            var references = base.GetScriptReferences().ToList();
            references.Add(reference);
            return references;
        }

        #endregion

        #region IPostBackDataHandler 成员

        public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
        {
            if (Page.IsPostBack)
            {
                // 重新得到所有节点【有些数据可能在客户端加载到，此处需要重新得到所有节点】
                string nodesString = Context.Request[this.NodeValue_HiddenField_ClientID];
                if (!string.IsNullOrWhiteSpace(nodesString))
                {
                    this.RootNode = Util.DeserializeJson<TreeNode>(nodesString);
                }

                // 得到客户端选中的节点
                string behaviorStateString = Context.Request[this.BehaviorState_HiddenField_ClientID];
                if (!string.IsNullOrEmpty(behaviorStateString))
                {
                    this.BehaviorState = Util.DeserializeJson<TreeBehaviorState>(behaviorStateString);
                    if (!string.IsNullOrEmpty(this.BehaviorState.SelectedId))
                        this.SelectedNode = GetNodeById(this.BehaviorState.SelectedId);
                    else
                        this.SelectedNode = null;
                }
            }

            return false;
        }

        public void RaisePostDataChangedEvent()
        {
            //如果LoadPostData返回true，此方法将被调用，此处不处理
        }

        #endregion

        #region IPostBackEventHandler 成员

        public void RaisePostBackEvent(string eventArgument)
        {
            RaiseCallbackEvent(eventArgument);
        }

        #endregion

        #region ICallbackEventHandler 成员

        public string GetCallbackResult()
        {
            return null;
        }

        public void RaiseCallbackEvent(string eventArgument)
        {
            ExtJSEventArgs extJSEventArgs = Util.DeserializeJson<ExtJSEventArgs>(eventArgument);
            if (extJSEventArgs == null)
                return;

            switch (extJSEventArgs.EventName)
            {
                case "NodeClick":
                    {
                        if (NodeClick != null)
                        {
                            TreeNodeEventArgs args = Util.DeserializeJson<TreeNodeEventArgs>(extJSEventArgs.Arguments);
                            NodeClick.Invoke(this, args);
                        }

                        if (this.BehaviorState.IsSelectChanged && this.TriggerPostBackEvents.NodeSelectChanged && NodeSelectChanged != null)
                        {
                            TreeNodeSelectChangedEventArgs args = new TreeNodeSelectChangedEventArgs();
                            args.NodeId = this.BehaviorState.SelectedId;
                            args.SelectedId = this.BehaviorState.SelectedId;
                            args.SelectedPath = this.BehaviorState.SelectedPath;
                            args.OldSelectedId = this.BehaviorState.OldSelectedId;
                            args.OldSelectedPath = this.BehaviorState.OldSelectedPath;
                            NodeSelectChanged.Invoke(this, args);

                            this.BehaviorState.IsSelectChanged = false;
                            this.BehaviorState.OldSelectedId = string.Empty;
                            this.BehaviorState.OldSelectedPath = string.Empty;
                        }
                    }
                    break;
                case "NodeSelectChanged":
                    {
                        if (NodeSelectChanged != null)
                        {
                            TreeNodeSelectChangedEventArgs args = Util.DeserializeJson<TreeNodeSelectChangedEventArgs>(extJSEventArgs.Arguments);
                            NodeSelectChanged.Invoke(this, args);

                            this.BehaviorState.IsSelectChanged = false;
                            this.BehaviorState.OldSelectedId = string.Empty;
                            this.BehaviorState.OldSelectedPath = string.Empty;
                        }
                    }
                    break;
                case "NodeCheckChanged":
                    {
                        if (NodeCheckChanged != null)
                        {
                            TreeNodeCheckChangedEventArgs args = Util.DeserializeJson<TreeNodeCheckChangedEventArgs>(extJSEventArgs.Arguments);
                            NodeCheckChanged.Invoke(this, args);
                        }
                    }
                    break;
                case "NodeExpand":
                    {
                        if (NodeExpand != null)
                        {
                            TreeNodeEventArgs args = Util.DeserializeJson<TreeNodeEventArgs>(extJSEventArgs.Arguments);
                            NodeExpand.Invoke(this, args);
                        }
                    }
                    break;
                case "NodeCollapse":
                    {
                        if (NodeCollapse != null)
                        {
                            TreeNodeEventArgs args = Util.DeserializeJson<TreeNodeEventArgs>(extJSEventArgs.Arguments);
                            NodeCollapse.Invoke(this, args);
                        }
                    }
                    break;
                case "LoadChildren":
                    {
                        LoadChildrenEventArgs args = Util.DeserializeJson<LoadChildrenEventArgs>(extJSEventArgs.Arguments);
                        args.Node = this.GetNodeById(args.NodeId);
                        if (args.Node != null)
                        {
                            args.Node.expanded = true;
                            args.Node.loaded = true;
                            if (LoadChildren != null)
                                LoadChildren.Invoke(this, args);
                            if (NodeExpand != null)
                                NodeExpand.Invoke(this, args);
                        }
                    }
                    break;
            }
        }

        #endregion
    }
}
